package fr.emse.tatiana.scoresheetvisualisation;

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.draw2d.*;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.graph.Path;
import org.eclipse.draw2d.graph.ShortestPathRouter;


/**
 * Routes multiple connections around the children of a given container figure.
 * @author Whitney Sorenson
 * @author Randy Hudson
 * @since 3.1
 */
public final class ShortestPathConnectionRouter
	extends AbstractRouter
{

private class LayoutTracker extends LayoutListener.Stub {
	public void postLayout(IFigure container) {
		processLayout();
	}
	public void remove(IFigure child) {
		removeChild(child);
	}
	public void setConstraint(IFigure child, Object constraint) {
		addChild(child);
	}
}

private Map constraintMap = new HashMap();
private Map figuresToBounds;
private Map connectionToPaths;
private boolean isDirty;
private ShortestPathRouter algorithm = new ShortestPathRouter();
private IFigure container;
private Set staleConnections = new HashSet();
private LayoutListener listener = new LayoutTracker();

private FigureListener figureListener = new FigureListener() {
	public void figureMoved(IFigure source) {
		Rectangle newBounds = source.getBounds().getCopy();
		if (algorithm.updateObstacle((Rectangle)figuresToBounds.get(source), newBounds)) {
			queueSomeRouting();
			isDirty = true;
		}
			
		figuresToBounds.put(source, newBounds);
	}
};
private boolean ignoreInvalidate;

/**
 * Creates a new shortest path router with the given container. The container
 * contains all the figure's which will be treated as obstacles for the connections to
 * avoid. Any time a child of the container moves, one or more connections will be
 * revalidated to process the new obstacle locations. The connections being routed must
 * not be contained within the container.
 * 
 * @param container the container
 */
public ShortestPathConnectionRouter(IFigure container) {
	isDirty = false;
	algorithm = new ShortestPathRouter();
	this.container = container;
}

void addChild(IFigure child) {
	if (connectionToPaths == null)
		return;
	if (figuresToBounds.containsKey(child))
		return;
	Rectangle bounds = child.getBounds().getCopy();
	algorithm.addObstacle(bounds);
	figuresToBounds.put(child, bounds);
	child.addFigureListener(figureListener);
	isDirty = true;
}

private void hookAll() {
	figuresToBounds = new HashMap();
	for (int i = 0; i < container.getChildren().size(); i++)
		addChild((IFigure)container.getChildren().get(i));
	container.addLayoutListener(listener);
}

private void unhookAll() {
	container.removeLayoutListener(listener);
	if (figuresToBounds != null) {
		Iterator figureItr = figuresToBounds.keySet().iterator();
		while (figureItr.hasNext()) {
			//Must use iterator's remove to avoid concurrent modification
			IFigure child = (IFigure)figureItr.next();
			figureItr.remove();
			removeChild(child);
		}
		figuresToBounds = null;
	}
}

/**
 * Gets the constraint for the given {@link Connection}.  The constraint is the paths
 * list of bend points for this connection.
 *
 * @param connection The connection whose constraint we are retrieving
 * @return The constraint
 */
public Object getConstraint(Connection connection) {
	return constraintMap.get(connection);
}

/**
 * Returns the default spacing maintained on either side of a connection. The default
 * value is 4.
 * @return the connection spacing
 * @since 3.2
 */
public int getSpacing() {
	return algorithm.getSpacing();
}

/**
 * @see ConnectionRouter#invalidate(Connection)
 */
public void invalidate(Connection connection) {
	if (ignoreInvalidate)
		return;
	staleConnections.add(connection);
	isDirty = true;
}

private void processLayout() {
	if (staleConnections.isEmpty())
		return;
	((Connection)staleConnections.iterator().next()).revalidate();
}

private void processStaleConnections() {
	Iterator iter = staleConnections.iterator();
	if (iter.hasNext() && connectionToPaths == null) {
		connectionToPaths = new HashMap();
		hookAll();
	}

	while (iter.hasNext()) {
		Connection conn = (Connection)iter.next();

		Path path = (Path)connectionToPaths.get(conn);
		if (path == null) {
			path = new Path(conn);
			connectionToPaths.put(conn, path);
			algorithm.addPath(path);
		}

		List constraint = (List)getConstraint(conn);
		if (constraint == null)
			constraint = Collections.EMPTY_LIST;
		
		Point start = conn.getSourceAnchor().getReferencePoint().getCopy();
		Point end = conn.getTargetAnchor().getReferencePoint().getCopy();

		container.translateToRelative(start);
		container.translateToRelative(end);
		
		path.setStartPoint(start);
		path.setEndPoint(end);
		
		if (!constraint.isEmpty()) {
			PointList bends = new PointList(constraint.size());
			for (int i = 0; i < constraint.size(); i++) {
				Bendpoint bp = (Bendpoint)constraint.get(i);
				bends.addPoint(bp.getLocation());
			}
			path.setBendPoints(bends);
		} else
			path.setBendPoints(null);
		
		isDirty |= path.isDirty;
	}
	staleConnections.clear();
}

void queueSomeRouting() {
	if (connectionToPaths == null || connectionToPaths.isEmpty())
		return;
	try {
		ignoreInvalidate = true;
		((Connection)connectionToPaths.keySet().iterator().next())
			.revalidate();
	} finally {
		ignoreInvalidate = false;
	}
}

/**
 * @see ConnectionRouter#remove(Connection)
 */
public void remove(Connection connection) {
	staleConnections.remove(connection);
	constraintMap.remove(connection);
	if (connectionToPaths == null)
		return;
	Path path = (Path)connectionToPaths.remove(connection);
	algorithm.removePath(path);
	isDirty = true;
	if (connectionToPaths.isEmpty()) {
		unhookAll();
		connectionToPaths = null;
	} else {
		//Make sure one of the remaining is revalidated so that we can re-route again.
		queueSomeRouting();
	}
}

void removeChild(IFigure child) {
	if (connectionToPaths == null)
		return;
	Rectangle bounds = child.getBounds().getCopy();
	boolean change = false;
	try {
		algorithm.removeObstacle(bounds);
	} catch (IndexOutOfBoundsException ex) {
		// bug in router code?
	}
	figuresToBounds.remove(child);
	child.removeFigureListener(figureListener);
	if (change) {
		isDirty = true;
		queueSomeRouting();
	}
}

/**
 * @see ConnectionRouter#route(Connection)
 */
public void route(Connection conn) {
	if (isDirty) {
		ignoreInvalidate = true;
		processStaleConnections();
		isDirty = false;
		List updated = algorithm.solve();
		Connection current;
		for (int i = 0; i < updated.size(); i++) {
			Path path = (Path) updated.get(i);
			current = (Connection)path.data;
			current.revalidate();
			
			PointList points = path.getPoints().getCopy();
			Point ref1, ref2, start, end;
			ref1 = new PrecisionPoint(points.getPoint(1));
			ref2 = new PrecisionPoint(points.getPoint(points.size() - 2));
			current.translateToAbsolute(ref1);
			current.translateToAbsolute(ref2);
			
			start = current.getSourceAnchor().getLocation(ref1).getCopy();
			end = current.getTargetAnchor().getLocation(ref2).getCopy();
			
			current.translateToRelative(start);
			current.translateToRelative(end);
			points.setPoint(start, 0);
			points.setPoint(end, points.size() - 1);

			current.setPoints(points);
		}
		ignoreInvalidate = false;
	}
}

/**
 * @see ConnectionRouter#setConstraint(Connection, Object)
 */
public void setConstraint(Connection connection, Object constraint) {
	//Connection.setConstraint() already calls revalidate, so we know that a
	// route() call will follow.
	staleConnections.add(connection);
	constraintMap.put(connection, constraint);
	isDirty = true;
}

/**
 * Sets the default space that should be maintained on either side of a connection. This
 * causes the connections to be separated from each other and from the obstacles. The
 * default value is 4.
 * 
 * @param spacing the connection spacing
 * @since 3.2
 */
public void setSpacing(int spacing) {
	algorithm.setSpacing(spacing);
}

}
